package com.zhigh.project.security.jwt.web.token;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BearerTokenExtractor implements TokenExtractor {

    private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE);

    private boolean allowFormEncodedBodyParameter = false;

    private boolean allowUriQueryParameter = false;

    private String formEncodedBodyParameterOrUriQueryParameterTokenName = "access_token";

    private String bearerTokenHeaderName = "Authorization";

    public BearerTokenExtractor() {
    }

    public BearerTokenExtractor(String bearerTokenHeaderName) {
        this.bearerTokenHeaderName = bearerTokenHeaderName;
    }

    public BearerTokenExtractor(String formEncodedBodyParameterOrUriQueryParameterTokenName, String bearerTokenHeaderName) {
        this(true, true, formEncodedBodyParameterOrUriQueryParameterTokenName, bearerTokenHeaderName);
    }

    public BearerTokenExtractor(boolean allowFormEncodedBodyParameter, boolean allowUriQueryParameter, String formEncodedBodyParameterOrUriQueryParameterTokenName, String bearerTokenHeaderName) {
        this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter;
        this.allowUriQueryParameter = allowUriQueryParameter;
        this.formEncodedBodyParameterOrUriQueryParameterTokenName = formEncodedBodyParameterOrUriQueryParameterTokenName;
        this.bearerTokenHeaderName = bearerTokenHeaderName;
    }

    @Override
    public String extract(HttpServletRequest request) {
        String authorizationHeaderToken = this.resolveFromAuthorizationHeader(request);
        String parameterToken = isParameterTokenEnabledForRequest(request) ? resolveFromRequestParameters(request) : null;
        if (authorizationHeaderToken != null) {
            if (parameterToken != null) {
                throw new TokenMultipleException("Found multiple bearer tokens in the request");
            } else {
                return authorizationHeaderToken;
            }
        } else {
            return parameterToken;
        }
    }

    private String resolveFromAuthorizationHeader(HttpServletRequest request) {
        String authorization = request.getHeader(this.bearerTokenHeaderName);
        if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
            return null;
        } else {
            Matcher matcher = authorizationPattern.matcher(authorization);
            if (!matcher.matches()) {
                throw new TokenFormatException("Bearer token is malformed");
            } else {
                return matcher.group("token");
            }
        }
    }

    private String resolveFromRequestParameters(HttpServletRequest request) {
        String[] values = request.getParameterValues(formEncodedBodyParameterOrUriQueryParameterTokenName);
        if (values != null && values.length != 0) {
            if (values.length == 1) {
                return values[0];
            } else {
                throw new TokenMultipleException("Found multiple bearer tokens in the request");
            }
        } else {
            return null;
        }
    }

    public boolean isAllowFormEncodedBodyParameter() {
        return allowFormEncodedBodyParameter;
    }

    public boolean isAllowUriQueryParameter() {
        return allowUriQueryParameter;
    }

    public String getFormEncodedBodyParameterOrUriQueryParameterTokenName() {
        return formEncodedBodyParameterOrUriQueryParameterTokenName;
    }

    public String getBearerTokenHeaderName() {
        return bearerTokenHeaderName;
    }

    private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {
        return this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod()) && "application/x-www-form-urlencoded".equals(request.getContentType()) || this.allowUriQueryParameter && "GET".equals(request.getMethod());
    }
}
